在這個迅速數位化的時代,會員系統逐漸在各網站普及,而「註冊/登入/登出」是這個系統最基本的功能。但由於安全性考量,大多會找第三方資源來串接。然而理解這個操作過程,有助於未來開發其他相關功能時,擁有足夠的背景知識。本篇筆記則紀錄我手刻登入機制後的反思。
本篇筆記將解決以下問題:
誰適合閱讀:
參考資料:
登入功能概念圖 from Alpha Camp's material
用遊樂園來比喻,登入機制就是購票入場的概念。當我們使用帳號密碼登入後,會得到一個憑證,以使用任何網站提供的會員服務及功能。
由於 HTTP 的既有限制,通常拆成兩階段來實作:
帳密檢查機制流程圖 from Alpha Camp's material
將流程拆解,帳密檢查分兩個步驟:
app.post('/login', (req, res) => {
const account = req.body
const result = checkoutUser(account)
if (result.status === 200) {
res.redirect(`/dashboard?user=${result.user}`)
} else {
res.status(401).render('login', { account })
}
})
function checkoutUser(account) {
// Define users data
// ...
// Checkout account
const result = users.find(user => user.email === account.email && user.password === account.password)
// Define response
const response = {
user: null,
status: 100
}
if (result) {
response.user = result.firstName
response.status = 200
} else {
response.status = 401
}
return response
}
保存使用者登入狀態流程圖 from Alpha Camp's material
做出這階段功能的關鍵是「讓 server 辨識:現在收到的 request 與稍早登入的 request 是同一位使用者」。
然在 HTTP 最早的設計中,每個 request 都是獨立判斷、並根據路由設計做出回應。之前的連線不需要被紀錄,這就是所謂「HTTP Protocol is Stateless」。
為了克服「Stateless」,這次的練習採用相對簡單的 Cookie-based Authentication 來設計的憑證機制。
cookie & session 示意圖 from Alpha Camp's material
我覺得憑證的概念,本身不難理解,重點在於不熟悉如何操作 cookie 和 session,於是在攻略完相關觀念後,直接 Google 「Cookie-based Authentication express」就能找到很多相關資料了。而我採用 Cookie-Parser 和 Express-Session 這兩個套件來輔助。
完整程式碼可以參考我的 GitHub repo
const cookieParser = require('cookie-parser')
const session = require('express-session')
// initialize cookie-parser to allow us access the cookies stored in the browser.
app.use(cookieParser())
// initialize express-session to allow us track the logged-in user across sessions.
app.use(session({
key: 'user_sid',
secret: 'someRandomStuffs',
resave: false,
saveUninitialized: false,
cookie: {
expires: 6000000
}
}))
// This middleware will check if user's cookie is still saved in browser and session is not set, then automatically log the user out.
// This usually happens when you stop your express server after login, your cookie still remains saved in the browser.
app.use((req, res, next) => {
if (req.cookies.user_sid && !req.session.user) {
res.clearCookie('user_sid')
}
next()
})
// middleware function to check for logged-in users
const sessionChecker = (req, res, next) => {
if (req.session.user && req.cookies.user_sid) {
const account = req.session.user
const result = checkoutUser(account)
const user = result.user
res.redirect(`/dashboard?user=${user}`)
} else {
next()
}
}
app.post('/login', (req, res) => {
const account = req.body
const result = checkoutUser(account)
if (result.status === 200) {
req.session.user = account
res.redirect(`/dashboard?user=${result.user}`)
} else {
res.status(401).render('login', { account })
}
})
// route for user's dashboard
app.get('/dashboard', (req, res) => {
if (req.session.user && req.cookies.user_sid) {
const user = req.query.user
res.render('dashboard', { user })
} else {
res.redirect('/login')
}
})
// route for user logout
app.get('/logout', (req, res) => {
if (req.session.user && req.cookies.user_sid) {
res.clearCookie('user_sid')
res.redirect('/')
} else {
res.redirect('/login')
}
})
關於本系列更多內容及導讀,請閱讀作者於 Medium 個人專欄 【無限賽局玩家 Infinite Gamer | Publication – 】 上的文章 《用 JavaScript 打造全端產品的入門學習筆記》系列指南。